import sys
import can
import logging
import queue
from datetime import datetime
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                            QHBoxLayout, QPushButton, QComboBox, QTableWidget, 
                            QTableWidgetItem, QLabel, QTextEdit, QTabWidget)
from PyQt6.QtCore import QTimer, pyqtSignal, QThread
import serial.tools.list_ports  # 用于检测串口
import canopen
import time
import os

# 设置日志配置
logging.basicConfig(
    filename='can_monitor.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    encoding='utf-8'
)

class CANWorker(QThread):
    """CAN通信工作线程"""
    message_received = pyqtSignal(object)
    error_occurred = pyqtSignal(str)

    def __init__(self, channel='can0', bitrate=500000):
        super().__init__()
        self.channel = channel
        self.bitrate = bitrate
        self.running = False
        self.bus = None

    def run(self):
        try:
            self.bus = can.interface.Bus(
                channel=self.channel,
                interface='slcan',
                bitrate=self.bitrate
            )
            self.running = True

            while self.running:
                message = self.bus.recv(timeout=0.1)
                if message:
                    self.message_received.emit(message)
        except Exception as e:
            self.error_occurred.emit(str(e))
            logging.error(f"CAN通信错误: {e}")

    def stop(self):
        self.running = False
        if self.bus:
            self.bus.shutdown()

class CANMonitorGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('CAN设备工具')
        self.setGeometry(100, 100, 1200, 800)
        self.can_worker = None
        self.message_queue = queue.Queue()
        self.is_saving = False
        self.initUI()

    def initUI(self):
        # 创建中央部件和主布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)

        # 创建选项卡
        tab_widget = QTabWidget()
        layout.addWidget(tab_widget)

        # 监控页面
        monitor_tab = QWidget()
        monitor_layout = QVBoxLayout(monitor_tab)

        # 左侧布局：控制面板和消息表格
        left_layout = QVBoxLayout()

        # 控制面板
        control_panel = QHBoxLayout()

        # 初始化串口下拉框
        self.interface_combo = QComboBox()
        self.refresh_ports()  

        # 串口相关控件
        port_label = QLabel('串口:')
        port_label.setFixedWidth(40)  # 设置标签固定宽度
        control_panel.addWidget(port_label)
        self.interface_combo.setFixedWidth(80)
        control_panel.addWidget(self.interface_combo)

        # 刷新串口按钮
        refresh_button = QPushButton('刷新串口')
        refresh_button.clicked.connect(self.refresh_ports)
        refresh_button.setFixedWidth(80)  # 调整按钮宽度
        control_panel.addWidget(refresh_button)

        # 波特率相关控件
        bitrate_label = QLabel('波特率:')
        bitrate_label.setFixedWidth(50)  # 设置标签固定宽度
        control_panel.addWidget(bitrate_label)
        self.bitrate_combo = QComboBox()
        self.bitrate_combo.addItems(['125000', '250000', '500000', '1000000'])
        control_panel.addWidget(self.bitrate_combo)

        # 连接按钮（原打开串口按钮）
        self.open_port_button = QPushButton('连接')
        self.open_port_button.clicked.connect(self.connect_can)  # 使用新的connect_can函数
        self.open_port_button.setFixedWidth(80)
        control_panel.addWidget(self.open_port_button)

        # 扫描节点按钮
        scan_button = QPushButton('扫描节点')
        scan_button.clicked.connect(self.scan_nodes)
        scan_button.setFixedWidth(80)
        control_panel.addWidget(scan_button)

        # 开始监控按钮
        self.start_button = QPushButton('开始监控')
        self.start_button.clicked.connect(self.toggle_monitoring)
        self.start_button.setFixedWidth(80)
        control_panel.addWidget(self.start_button)

        # 开始保存按钮
        self.save_button = QPushButton('开始保存')
        self.save_button.clicked.connect(self.toggle_saving)  # 你需要实现这个方法
        self.save_button.setFixedWidth(80)
        control_panel.addWidget(self.save_button)

        # ���所有控件添加完成后，添加弹性空间
        control_panel.addStretch(1)  # 添加弹性空间，使所有控件靠左对齐

        # 添加到左侧布局
        left_layout.addLayout(control_panel)

        # 消息表格
        self.message_table = QTableWidget()
        self.message_table.setColumnCount(7)
        self.message_table.setHorizontalHeaderLabels(
            ['时间戳', '节点ID', '类型', '长度', '数据', '解析值', '状态'])
        # 设置表格的大小策略
        self.message_table.setMinimumWidth(600)  # 设置最小宽度
        self.message_table.setMaximumWidth(800)  # 设置最大宽度
        self.message_table.setMinimumHeight(400)  # 设置最小高度
        self.message_table.setMaximumHeight(600)  # 设置最大高度

        # 设置列宽
        self.message_table.setColumnWidth(0, 100)  # 时间戳列
        self.message_table.setColumnWidth(1, 80)   # 节点ID列
        self.message_table.setColumnWidth(2, 60)   # 类型列
        self.message_table.setColumnWidth(3, 60)   # 长度列
        self.message_table.setColumnWidth(4, 150)  # 数据列
        self.message_table.setColumnWidth(5, 100)  # 解析值列
        self.message_table.setColumnWidth(6, 60)   # 状态列

        left_layout.addWidget(self.message_table)

        # 将左侧布局添���到监控布局
        monitor_layout.addLayout(left_layout)

        # 右侧输出区域
        self.output_area = QTextEdit()
        self.output_area.setReadOnly(True)
        monitor_layout.addWidget(self.output_area)
        # 添加
        tab_widget.addTab(monitor_tab, "首页")

        # 节点配置页面
        config_tab = QWidget()
        config_layout = QVBoxLayout(config_tab)

        # 节点列表
        self.node_table = QTableWidget()
        self.node_table.setColumnCount(4)
        self.node_table.setHorizontalHeaderLabels(['节点ID', '状态', '波特率', '操作'])
        config_layout.addWidget(self.node_table)

        tab_widget.addTab(config_tab, "配置")

        # 节点测试页面
        config_tab = QWidget()
        config_layout = QVBoxLayout(config_tab)
        

        tab_widget.addTab(config_tab, "测试")

        # # ... existing code ...

        # 日志页面
        log_tab = QWidget()
        log_layout = QVBoxLayout(log_tab)

        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True) 
        log_layout.addWidget(self.log_text)

      

        tab_widget.addTab(log_tab, "日志")

        # 状态栏
        self.statusBar().showMessage('就绪')

    def toggle_monitoring(self):
        if not self.can_worker:
            # 开始监控
            channel = self.interface_combo.currentText()
            bitrate = int(self.bitrate_combo.currentText())

            try:
                self.can_worker = CANWorker(channel, bitrate)
                self.can_worker.message_received.connect(self.process_message)
                self.can_worker.error_occurred.connect(self.handle_error)
                self.can_worker.start()

                self.start_button.setText('停止监控')
                self.statusBar().showMessage('正在监控CAN总线...')
                logging.info(f"开始监控CAN总线 - 接口: {channel}, 波特率: {bitrate}")

            except Exception as e:
                self.handle_error(str(e))

        else:
            # 停止监控
            self.can_worker.stop()
            self.can_worker = None
            self.start_button.setText('开始监控')
            self.statusBar().showMessage('监控已停止')
            logging.info("停止监控CAN总线")

    def process_message(self, msg):
        """处理接收到的CAN消息"""
        try:
            row = self.message_table.rowCount()
            self.message_table.insertRow(row)

            # 获取消息数据
            timestamp = datetime.fromtimestamp(msg.timestamp).strftime('%H:%M:%S.%f')[:-3]
            node_id = f"0x{msg.arbitration_id:X}"
            msg_type = 'RTR' if msg.is_remote_frame else 'DATA'
            data_hex = ' '.join(f"{b:02X}" for b in msg.data[:msg.dlc])

            # 添加消息数据到表格
            self.message_table.setItem(row, 0, QTableWidgetItem(timestamp))
            self.message_table.setItem(row, 1, QTableWidgetItem(node_id))
            self.message_table.setItem(row, 2, QTableWidgetItem(msg_type))
            self.message_table.setItem(row, 3, QTableWidgetItem(str(msg.dlc)))
            self.message_table.setItem(row, 4, QTableWidgetItem(data_hex))

            # 尝试解析数据
            try:
                parsed_value = self.parse_can_data(msg)
                self.message_table.setItem(row, 5, QTableWidgetItem(parsed_value))
            except Exception as e:
                self.message_table.setItem(row, 5, QTableWidgetItem("解析错误"))

            self.message_table.setItem(row, 6, QTableWidgetItem("正常"))

            # 如果正在保存数据，则将消息写入文件
            if self.is_saving and hasattr(self, 'save_file'):
                try:
                    save_line = f"{timestamp},{node_id},{msg_type},{msg.dlc},{data_hex},{parsed_value},正常\n"
                    self.save_file.write(save_line)
                    # 每100条消息才进行一次flush
                    if row % 100 == 0:
                        self.save_file.flush()
                except Exception as e:
                    logging.error(f"保存数据失败: {e}")

            # 更新日志
            log_msg = f"接收: ID={node_id}, 数据={data_hex}"
            logging.info(log_msg)
            self.log_text.append(f"{timestamp} - {log_msg}")

        except Exception as e:
            logging.error(f"处理消息失败: {e}")

    def parse_can_data(self, msg):
        """解析CAN数据"""
        # 这里可以根据不同的CAN ID实现不同的解析逻辑
        if msg.arbitration_id == 0x141:  # 电机状态1
            return f"温度={msg.data[0]}°C, 电压={msg.data[1]/10.0}V"
        elif msg.arbitration_id == 0x142:  # 电机状态2
            speed = int.from_bytes(msg.data[2:4], byteorder='little', signed=True)
            return f"速度={speed}rpm"
        return "未知数据格式"

    def scan_nodes(self):
        """扫描CAN节点"""
        if not self.interface_combo.currentText():
            self.statusBar().showMessage('请选择串口设备')
            logging.warning("未选择串口设备")
            return

        self.statusBar().showMessage('正在扫描节点...')
        logging.info("开始扫描CAN节点")

        # 空节点表格
        self.node_table.setRowCount(0)

        # 清空输出区域
        self.output_area.clear()

        try:
            # 创建CANopen网络
            network = canopen.Network()
            channel = self.interface_combo.currentText()
            bitrate = int(self.bitrate_combo.currentText())

            # 连接到CAN网络
            network.connect(channel=channel, bustype='slcan', bitrate=bitrate)
            time.sleep(2)  # 等待连接稳定

            # 扫描节点
            for node_id in network.scanner.nodes:
                row = self.node_table.rowCount()
                self.node_table.insertRow(row)

                self.node_table.setItem(row, 0, QTableWidgetItem(f"0x{node_id:X}"))
                self.node_table.setItem(row, 1, QTableWidgetItem("在线"))
                self.node_table.setItem(row, 2, QTableWidgetItem(f"{bitrate/1000}k"))

                config_button = QPushButton("配置")
                config_button.clicked.connect(lambda checked, nid=node_id: self.configure_node(nid))
                self.node_table.setCellWidget(row, 3, config_button)

                logging.info(f"发现节点: 0x{node_id:X}")
                self.output_area.append(f"发现节点: 0x{node_id:X}")  # 显示发现节点信息

            # 断开连接
            network.disconnect()

            if self.node_table.rowCount() == 0:
                self.statusBar().showMessage('未发现活动节点')
                logging.info("扫描完成：未发现活动节点")
                self.output_area.append("扫描完成：未发现活动节点")
            else:
                self.statusBar().showMessage(f'扫描完成，发现 {self.node_table.rowCount()} 个节点')
                logging.info(f"扫描完成：发现 {self.node_table.rowCount()} 个活动节点")
                self.output_area.append(f"扫描完成，发现 {self.node_table.rowCount()} 个活动节点")

        except Exception as e:
            error_msg = f"节点扫描出错: {str(e)}"
            self.statusBar().showMessage(error_msg)
            logging.error(error_msg)
            self.output_area.append(f"错误: {error_msg}")  # 显示错误信息

    def configure_node(self, node_id):
        """配置节点"""
        logging.info(f"配置节点 0x{node_id:X}")
        # 这里实现节点配置逻辑

    def handle_error(self, error_msg):
        """处理错误"""
        self.statusBar().showMessage(f'错误: {error_msg}')
        logging.error(error_msg)
        self.log_text.append(f"错误: {error_msg}")

    def refresh_ports(self):
        """刷新并更新可用串口列表"""
        self.interface_combo.clear()
        ports = [port.device for port in serial.tools.list_ports.comports()]
        if ports:
            self.interface_combo.addItems(ports)
            logging.info(f"发现可用串口: {', '.join(ports)}")
        else:
            logging.warning("未发现可用串口")
            self.interface_combo.addItem("未发现串口")

    def connect_can(self):
        """CAN接口连接处理"""
        if self.open_port_button.text() == '连接':
            try:
                # 获取选择的串口和波特率
                port = self.interface_combo.currentText()
                if not port:
                    self.statusBar().showMessage('请选择串口')
                    return
                    
                # 尝试打开串口
                ser = serial.Serial(
                    port=port,
                    baudrate=115200,  # USB-CAN转换器一般使用这个波特率
                    timeout=1
                )
                
                if ser.is_open:
                    ser.close()
                ser.open()
                
                # 更新UI状态
                self.open_port_button.setText('断开')
                self.interface_combo.setEnabled(False)
                self.bitrate_combo.setEnabled(False)
                self.statusBar().showMessage('CAN接口已连接')
                logging.info(f"已连接到CAN接口 - 串口: {port}")
                
            except Exception as e:
                self.statusBar().showMessage(f'连接失败: {str(e)}')
                logging.error(f"连接失败: {e}")
                
        else:  # 断开连接
            try:
                # 关闭串口
                if hasattr(self, 'ser') and self.ser.is_open:
                    self.ser.close()
                
                # 恢复UI状态
                self.open_port_button.setText('连接')
                self.interface_combo.setEnabled(True)
                self.bitrate_combo.setEnabled(True)
                self.statusBar().showMessage('CAN接口已断开')
                logging.info("已断开CAN接口")
                
            except Exception as e:
                self.statusBar().showMessage(f'断开失败: {str(e)}')
                logging.error(f"断开失败: {e}")

        # 在初始化时修改按钮连接的函数
        self.open_port_button.clicked.connect(self.connect_can)  # 替换原来的toggle_port

    def toggle_saving(self):
        """切换保存状态，保存已显示的日志"""
        if self.save_button.text() == '开始保存':
            try:
                # 创建保存目录（如果不存在）
                save_dir = 'can_logs'
                if not os.path.exists(save_dir):
                    os.makedirs(save_dir)
                
                # 创建带时间戳的文件名
                current_time = datetime.now().strftime('%Y%m%d_%H%M%S')
                filename = os.path.join(save_dir, f'can_log_{current_time}.txt')
                
                # 获取日志文本内容
                log_content = self.log_text.toPlainText()
                
                # 保存日志内容到文件
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write(log_content)
                
                self.statusBar().showMessage(f'日志已保存到文件: {filename}')
                logging.info(f"日志已保存到文件: {filename}")
                
            except Exception as e:
                self.statusBar().showMessage(f'保存日志失败: {str(e)}')
                logging.error(f"保存日志失败: {e}")

def main():
    app = QApplication(sys.argv)
    window = CANMonitorGUI()
    window.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()
